#include "Z80.h"
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Z80::Z80() {}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Z80::~Z80() {}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Z80::Reset() {
    m_PC.W = 0x0100;
    m_AF.W = 0x01B0;
    m_BC.W = 0x0013;
    m_DE.W = 0x00D8;
    m_HL.W = 0x014D;
    m_SP.W = 0xFFFE;
    m_Cycles = 0;
    m_TotalCycles = 0;
    m_PendingDI = false;
    m_PendingEI = false;
    m_IME = false;
    m_Halted = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Z80::HaltedStatus( bool Value ) {
    m_Halted = Value;
    
    // FIXME: en un futuro tener en cuenta lo siguiente:
    // de GB.pdf: "The instruction immediately following the HALT instruction is skipped when interrupts are
    // disabled (DI) on the GB,GBP, and SGB."
    
    // Salteo una instruccion si m_Halted se esta seteando a TRUE desde GameBoy.cpp y las interrupciones estan deshabilitadas
    if( !m_IME && !m_Halted ) m_PC.W++;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
u8 Z80::Fetch() {
    return m_Read( m_PC.W );
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int Z80::Execute( u8 Opcode ) {
    if( Opcode != 0xCB ) {
        printf( "\nPC:%4X  %4X  P:%2X  SP:%2X  CYC:%3i  ", m_PC.W, Opcode, m_AF.L, m_SP.W, m_TotalCycles );
    } else {
        printf( "\nPC:%4X  %2X%2X  P:%2X  SP:%2X  CYC:%3i  ", m_PC.W, Opcode, m_Read(m_PC.W + 1), m_AF.L, m_SP.W, m_TotalCycles );
    }
    
    /*if( Opcode != 0xCB ) {
        char buffer[200];
        sprintf( buffer, "\nPC:%X\t%X\tP:%X\tSP:%X\tA:%X\tB:%X\tC:%X\tD:%X\tE:%X\tH:%X\tL:%X", m_PC.W, Opcode, m_AF.L, m_SP.W, m_AF.H, m_BC.H, m_BC.L, m_DE.H, m_DE.L, m_HL.H, m_HL.L );
        FILE* fp = fopen( "C:\\emulator.txt", "at" );
        fputs( buffer, fp );
        fclose( fp );
    } else {
        printf( "\nPC:%X  %X%X  P:%X  SP:%X", m_PC.W, Opcode, m_Read(m_PC.W + 1), m_AF.L, m_SP.W );
    }*/
    
    // de gbsepc.txt: "The instruction immediately following the HALT instruction is skipped when interrupts are disabled (DI) on
    // the GB,GBP, and SGB. This does not seem to occur on the GameBoy Color even in regular GB mode. ($143=$00)"
    if( m_Halted ) m_Cycles = 4;
    else {
        m_PC.W++;
        switch( Opcode ) {
            case 0x00: break; // NOP
            case 0x01: m_BC.W = ReadWord(); break; // LD BC,nn
            case 0x02: m_Write( m_BC.W, m_AF.H ); break; // LD (BC),A
            case 0x03: m_BC.W++; break; // INC BC
            case 0x04: Increment_8Bit( m_BC.H ); break; // INC B
            case 0x05: Decrement_8Bit( m_BC.H ); break; // DEC B
            case 0x06: m_BC.H = m_Read( m_PC.W++ ); break; // LD B,n
            case 0x07: RotateLeft(); break; // RLCA
            case 0x0B: m_BC.W--; break; // DEC BC
            case 0x0C: Increment_8Bit( m_BC.L ); break; // INC C
            case 0x0D: Decrement_8Bit( m_BC.L ); break; // DEC C
            case 0x0E: m_BC.L = m_Read( m_PC.W++ ); break; // LD C,n
            case 0x0F: RotateRight(); break; // RRCA
            case 0x10: m_PC.W++; printf( "  ???" ); break; // FIXME: STOP??? verificar tambien la tabla de ciclos
            case 0x11: m_DE.W = ReadWord(); break; // LD DE,nn
            case 0x12: m_Write( m_DE.W, m_AF.H ); break; // LD (DE),A
            case 0x13: m_DE.W++; break; // INC DE
            case 0x14: Increment_8Bit( m_DE.H ); break; // INC D
            case 0x16: m_DE.H = m_Read( m_PC.W++ ); break; // LD D,n
            case 0x17: RotateLeftWithCarry(); break; // RLA
            case 0x18: m_PC.W += (s8)m_Read( m_PC.W ); m_PC.W++; break; // JR n
            case 0x1B: m_DE.W--; break; // DEC DE
            case 0x1C: Increment_8Bit( m_DE.L ); break; // INC E
            case 0x1D: Decrement_8Bit( m_DE.L ); break; // DEC E
            case 0x1E: m_DE.L = m_Read( m_PC.W++ ); break; // LD E,n
            case 0x1F: RotateRightWithCarry(); break; // RRA
            case 0x20: if( !TestFlag(m_AF.L, FLAG_Z) ) m_PC.W += (s8)m_Read( m_PC.W ); m_PC.W++; break; // JP NZ,*
            case 0x21: m_HL.W = ReadWord(); break; // LD HL,nn
            case 0x23: m_HL.W++; break; // INC HL
            case 0x24: Increment_8Bit( m_HL.H ); break; // INC H
            case 0x26: m_HL.H = m_Read( m_PC.W++ ); break; // LD H,n
            case 0x27: DecimalAdjust(); break; // DAA
            case 0x28: if( TestFlag(m_AF.L, FLAG_Z) ) m_PC.W += (s8)m_Read( m_PC.W ); m_PC.W++; break; // JP Z,*
            case 0x2A: m_AF.H = m_Read( m_HL.W++ ); break; // LDI A,(HL)
            case 0x2B: m_HL.W--; break; // DEC HL
            case 0x2C: Increment_8Bit( m_HL.L ); break; // INC L
            case 0x2D: Decrement_8Bit( m_HL.L ); break; // DEC L
            case 0x2E: m_HL.L = m_Read( m_PC.W++ ); break; // LD L,n
            case 0x2F: m_AF.H ^= 0xFF; m_AF.L |= ( FLAG_N | FLAG_H ); break; // CPL
            case 0x30: if( !TestFlag(m_AF.L, FLAG_C) ) m_PC.W += (s8)m_Read( m_PC.W ); m_PC.W++; break; // JP NC,*
            case 0x31: m_SP.W = ReadWord(); break; // LD SP,nn
            case 0x32: m_Write( m_HL.W--, m_AF.H ); break; // LDD (HL),A
            case 0x33: m_SP.W++; break; // INC SP
            case 0x36: m_Write( m_HL.W, m_Read(m_PC.W) ); break; // LD (HL),n
            case 0x37: m_AF.L &= ~( FLAG_N | FLAG_H ); m_AF.L |= FLAG_C; break; // SCF
            case 0x38: if( TestFlag(m_AF.L, FLAG_C) ) m_PC.W += (s8)m_Read( m_PC.W ); m_PC.W++; break; // JP C,*
            case 0x3A: m_AF.H = m_Read( m_HL.W-- ); break; // LDD A,(HL)
            case 0x3B: m_SP.W--; break; // DEC SP
            case 0x3C: Increment_8Bit( m_AF.H ); break; // INC A
            case 0x3D: Decrement_8Bit( m_AF.H ); break; // DEC A
            case 0x3E: m_AF.H = m_Read( m_PC.W++ ); break; // LD A,#
            case 0x3F: m_AF.L &= ~( FLAG_N | FLAG_H ); m_AF.L ^= FLAG_C; break; // CCF
            case 0x40: m_BC.H = m_BC.H; break; // LD B,B
            case 0x41: m_BC.H = m_BC.L; break; // LD B,C
            case 0x42: m_BC.H = m_DE.H; break; // LD B,D
            case 0x43: m_BC.H = m_DE.L; break; // LD B,E
            case 0x44: m_BC.H = m_HL.H; break; // LD B,H
            case 0x45: m_BC.H = m_HL.L; break; // LD B,L
            case 0x46: m_BC.H = m_Read( m_HL.W ); break; // LD B,(HL)
            case 0x47: m_BC.H = m_AF.H; break; // LD B,A
            case 0x48: m_BC.L = m_BC.H; break; // LD C,B
            case 0x49: m_BC.L = m_BC.L; break; // LD C,C
            case 0x4A: m_BC.L = m_DE.H; break; // LD C,D
            case 0x4B: m_BC.L = m_DE.L; break; // LD C,E
            case 0x4C: m_BC.L = m_HL.H; break; // LD C,H
            case 0x4D: m_BC.L = m_HL.L; break; // LD C,L
            case 0x4E: m_BC.L = m_Read( m_HL.W ); break; // LD C,(HL)
            case 0x4F: m_BC.L = m_AF.H; break; // LD C,A
            case 0x50: m_DE.H = m_BC.H; break; // LD D,B
            case 0x51: m_DE.H = m_BC.L; break; // LD D,C
            case 0x52: m_DE.H = m_DE.H; break; // LD D,D
            case 0x53: m_DE.H = m_DE.L; break; // LD D,E
            case 0x54: m_DE.H = m_HL.H; break; // LD D,H
            case 0x55: m_DE.H = m_HL.L; break; // LD D,L
            case 0x56: m_DE.H = m_Read( m_HL.W ); break; // LD D,(HL)
            case 0x57: m_DE.H = m_AF.H; break; // LD D,A
            case 0x58: m_DE.L = m_BC.H; break; // LD E,B
            case 0x59: m_DE.L = m_BC.L; break; // LD E,C
            case 0x5A: m_DE.L = m_DE.H; break; // LD E,D
            case 0x5B: m_DE.L = m_DE.L; break; // LD E,E
            case 0x5C: m_DE.L = m_HL.H; break; // LD E,H
            case 0x5D: m_DE.L = m_HL.L; break; // LD E,L
            case 0x5E: m_DE.L = m_Read( m_HL.W ); break; // LD E,(HL)
            case 0x5F: m_DE.L = m_AF.H; break; // LD E,A
            case 0x60: m_HL.H = m_BC.H; break; // LD H,B
            case 0x61: m_HL.H = m_BC.L; break; // LD H,C
            case 0x62: m_HL.H = m_DE.H; break; // LD H,D
            case 0x63: m_HL.H = m_DE.L; break; // LD H,E
            case 0x64: m_HL.H = m_HL.H; break; // LD H,H
            case 0x65: m_HL.H = m_HL.L; break; // LD H,L
            case 0x66: m_HL.H = m_Read( m_HL.W ); break; // LD H,(HL)
            case 0x67: m_HL.H = m_AF.H; break; // LD H,A
            case 0x68: m_HL.L = m_BC.H; break; // LD L,B
            case 0x69: m_HL.L = m_BC.L; break; // LD L,C
            case 0x6A: m_HL.L = m_DE.H; break; // LD L,D
            case 0x6B: m_HL.L = m_DE.L; break; // LD L,E
            case 0x6C: m_HL.L = m_HL.H; break; // LD L,H
            case 0x6D: m_HL.L = m_HL.L; break; // LD L,L
            case 0x6E: m_HL.L = m_Read( m_HL.W ); break; // LD L,(HL)
            case 0x6F: m_HL.L = m_AF.H; break; // LD L,A
            case 0x70: m_Write( m_HL.W, m_BC.H ); break; // LD (HL),B
            case 0x71: m_Write( m_HL.W, m_BC.L ); break; // LD (HL),C
            case 0x72: m_Write( m_HL.W, m_DE.H ); break; // LD (HL),D
            case 0x73: m_Write( m_HL.W, m_DE.L ); break; // LD (HL),E
            case 0x74: m_Write( m_HL.W, m_HL.H ); break; // LD (HL),H
            case 0x75: m_Write( m_HL.W, m_HL.L ); break; // LD (HL),L
            case 0x76: m_Halted = true; break; // HALT
            case 0x77: m_Write( m_HL.W, m_AF.H ); break; // LD (HL),A
            case 0x78: m_AF.H = m_BC.H; break; // LD A,B
            case 0x79: m_AF.H = m_BC.L; break; // LD A,C
            case 0x7A: m_AF.H = m_DE.H; break; // LD A,D
            case 0x7B: m_AF.H = m_DE.L; break; // LD A,E
            case 0x7C: m_AF.H = m_HL.H; break; // LD A,H
            case 0x7D: m_AF.H = m_HL.L; break; // LD A,L
            case 0x7E: m_AF.H = m_Read( m_HL.W ); break; // LD A,(HL)
            case 0x7F: m_AF.H = m_AF.H; break; // LD A,A
            case 0x80: Add( m_BC.H ); break; // ADD A,B
            case 0x81: Add( m_BC.L ); break; // ADD A,C
            case 0x82: Add( m_DE.H ); break; // ADD A,D
            case 0x83: Add( m_DE.L ); break; // ADD A,E
            case 0x84: Add( m_HL.H ); break; // ADD A,H
            case 0x85: Add( m_HL.L ); break; // ADD A,L
            case 0x86: Add( m_Read(m_HL.W) ); break; // ADD A,(HL)
            case 0x87: Add( m_AF.H ); break; // ADD A,A
            case 0x88: AddWithCarry( m_BC.H ); break; // ADC A,B
            case 0x89: AddWithCarry( m_BC.L ); break; // ADC A,C
            case 0x8A: AddWithCarry( m_DE.H ); break; // ADC A,D
            case 0x8B: AddWithCarry( m_DE.L ); break; // ADC A,E
            case 0x8C: AddWithCarry( m_HL.H ); break; // ADC A,H
            case 0x8D: AddWithCarry( m_HL.L ); break; // ADC A,L
            case 0x8E: AddWithCarry( m_Read(m_HL.W) ); break; // ADC A,(HL)
            case 0x8F: AddWithCarry( m_AF.H ); break; // ADC A,A
            case 0x98: SubtractWithCarry( m_BC.H ); break; // SBC A,B
            case 0x99: SubtractWithCarry( m_BC.L ); break; // SBC A,C
            case 0x9A: SubtractWithCarry( m_DE.H ); break; // SBC A,D
            case 0x9B: SubtractWithCarry( m_DE.L ); break; // SBC A,E
            case 0x9C: SubtractWithCarry( m_HL.H ); break; // SBC A,H
            case 0x9D: SubtractWithCarry( m_HL.L ); break; // SBC A,L
            case 0x9E: SubtractWithCarry( m_Read(m_HL.W) ); break; // SBC A,(HL)
            case 0x9F: SubtractWithCarry( m_AF.H ); break; // SBC A,A
            case 0xA0: AND( m_BC.H ); break; // AND B
            case 0xA1: AND( m_BC.L ); break; // AND C
            case 0xA2: AND( m_DE.H ); break; // AND D
            case 0xA3: AND( m_DE.L ); break; // AND E
            case 0xA4: AND( m_HL.H ); break; // AND H
            case 0xA5: AND( m_HL.L ); break; // AND L
            case 0xA6: AND( m_Read(m_HL.W) ); break; // AND (HL)
            case 0xA7: AND( m_AF.H ); break; // AND A
            case 0xA8: XOR( m_BC.H ); break; // XOR A,B
            case 0xA9: XOR( m_BC.L ); break; // XOR A,C
            case 0xAA: XOR( m_DE.H ); break; // XOR A,D
            case 0xAB: XOR( m_DE.L ); break; // XOR A,E
            case 0xAC: XOR( m_HL.H ); break; // XOR A,H
            case 0xAD: XOR( m_HL.L ); break; // XOR A,L
            case 0xAE: XOR( m_Read(m_HL.W) ); break; // XOR A,(HL)
            case 0xAF: XOR( m_AF.H ); break; // XOR A,A
            case 0xB0: OR( m_BC.H ); break; // OR B
            case 0xB1: OR( m_BC.L ); break; // OR C
            case 0xB2: OR( m_DE.H ); break; // OR D
            case 0xB3: OR( m_DE.L ); break; // OR E
            case 0xB4: OR( m_HL.H ); break; // OR H
            case 0xB5: OR( m_HL.L ); break; // OR L
            case 0xB6: OR( m_Read(m_HL.W) ); break; // OR (HL)
            case 0xB7: OR( m_AF.H ); break; // OR A
            case 0xC0: if( !TestFlag(m_AF.L, FLAG_Z) ) m_PC.W = PopWord(); break; // RET NZ
            case 0xC3: m_PC.W = ReadWord(); break; // JP nn
            case 0xC4: if( !TestFlag(m_AF.L, FLAG_Z) ) Call( ReadWord() ); break; // CALL NZ,nn
            case 0xC5: PushWord( m_BC.W ); break; // PUSH BC
            case 0xC6: Add( m_Read(m_PC.W++) ); break; // ADD A,#
            case 0xC8: if( TestFlag(m_AF.L, FLAG_Z) ) m_PC.W = PopWord(); break; // RET Z
            case 0xC9: m_PC.W = PopWord(); break; // RET
            case 0xCB: Opcode = m_Read( m_PC.W++ ); ExecuteExtended( Opcode ); break; // Opcodes extentidos
            case 0xCE: AddWithCarry( m_Read(m_PC.W++) ); break; // ADC A,#
            case 0xCC: if( TestFlag(m_AF.L, FLAG_Z) ) Call( ReadWord() ); break; // CALL Z,nn
            case 0xCD: Call( ReadWord() ); break; // CALL nn
            case 0xD0: if( !TestFlag(m_AF.L, FLAG_C) ) m_PC.W = PopWord(); break; // RET NC
            case 0xD4: if( !TestFlag(m_AF.L, FLAG_C) ) Call( ReadWord() ); break; // CALL NC,nn
            case 0xD5: PushWord( m_DE.W ); break; // PUSH DE
            case 0xD8: if( TestFlag(m_AF.L, FLAG_C) ) m_PC.W = PopWord(); break; // RET C
            case 0xDC: if( TestFlag(m_AF.L, FLAG_C) ) Call( ReadWord() ); break; // CALL C,nn
            case 0xDE: SubtractWithCarry( m_Read(m_PC.W++) ); break; // SBC A,#
            case 0xE0: m_Write( 0xFF00 + m_Read(m_PC.W++), m_AF.H ); break; // LDH (n),A
            case 0xE2: m_Write( 0xFF00 + m_BC.L, m_AF.H ); break; // LD (C),A
            case 0xE5: PushWord( m_HL.W ); break; // PUSH HL
            case 0xE6: AND( m_Read(m_PC.W++) ); break; // AND #
            case 0xE9: m_PC.W = m_HL.W; break; // JP (HL)
            case 0xEE: XOR( m_Read(m_PC.W++) ); break; // XOR A,#
            case 0xEA: m_Write( ReadWord(), m_AF.H ); break; // LD (nn),A
            case 0xF0: m_AF.H = m_Read( 0xFF00 + m_Read(m_PC.W++) ); break; // LDH A,(n)
            case 0xF3: m_PendingDI = true; break; // DI
            case 0xF5: PushWord( m_AF.W ); break; // PUSH AF
            case 0xF6: OR( m_Read(m_PC.W++) ); break; // OR #
            case 0xF8: LDHL_SPn(); break; // LDHL SP,n
            case 0xFB: m_PendingEI = true; break; // EI
            case 0xFE: Compare_8Bit( m_AF.H, m_Read(m_PC.W++) ); break; // CP #
            default: printf( "Opcode invalido!!!\n\n" ); exit( 0 );
        }
        
        if( Opcode != 0xCB ) m_Cycles = OpcodeCycles[Opcode];
        else m_Cycles = ExtendedOpcodeCycles[Opcode];
    }
    
    // de GB.PDF: "DI instruction disables interrupts but not immediately. Interrupts are disabled after instruction after DI is executed"
    if( m_PendingDI && (m_Read(m_PC.W - 1) !=  0xF3) ) {
        m_PendingDI = false;
        m_IME = false;
    }
    
    if( m_PendingEI && (m_Read(m_PC.W - 1) !=  0xFB) ) {
        m_PendingEI = false;
        m_IME = true;
    }
    
    m_TotalCycles += m_Cycles;
    
    // FIXME
    if( m_Cycles == 0 ) {
        printf( " Error en el calculo de ciclos!!!" );
        exit( 0 );
    }
    
    // FIXME
    //getch();
    
    return m_Cycles;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int Z80::ExecuteExtended( u8 Opcode ) {
    switch( Opcode ) {
        case 0x78: BIT( m_BC.H, 7 ); break; // BIT 7,B
        default: printf( "Opcode invalido!!!\n\n" ); exit( 0 );
    }
    
    // FIXME: estas linea esta solo para chequeo. el calculo de ciclos se hace en la funcion Execute.
    m_Cycles = ExtendedOpcodeCycles[Opcode];
    if( m_Cycles == 0 ) {
        printf( " Error en el calculo de ciclos!!!" );
        exit( 0 );
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
u16 Z80::ReadWord() {
    PairRegister temp;
    temp.L = m_Read( m_PC.W++ );
    temp.H = m_Read( m_PC.W++ );
    
    return temp.W;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Z80::PushWord( u16 Value ) {
    m_Write( --m_SP.W, Value >> 8 );
    m_Write( --m_SP.W, Value & 0x00FF );
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
u16 Z80::PopWord() {
    PairRegister temp;
    temp.L = m_Read( m_SP.W++ );
    temp.H = m_Read( m_SP.W++ );
    
    return temp.W;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////